﻿using DJLang;
using System.Collections;
using System.Collections.Generic;
using System.IO.Compression;
using System.IO;
using System.Text;
using UnityEngine.Networking;
using UnityEngine;
using System;

namespace BlockOS.Client.Res
{
    public class ResLoader
    {
        public static readonly string RESOURCES_DIR_NAME = "Resources";

        public ResourcePack combinedPack = new();
        public List<string> targetLoadPackPaths = new();

        private DJEnv modelBuildEnv;
        public ResLoader()
        {
            modelBuildEnv = new DJEnv();
            modelBuildEnv.LoadStandard(DJStdLoadFlags.Minimal);
            modelBuildEnv.PutLibrary(typeof(BlockModelLoadLib));
        }

        public void AddPackLoadTask(string path)
        {
            targetLoadPackPaths.Add(path);
        }

        public IEnumerator Load()
        {
            foreach (var path in targetLoadPackPaths)
            {
                yield return LoadPack(path);
            }
        }

        public IEnumerator LoadPack(string path)
        {   // 读取指定路径下的文件
            var req = UnityWebRequest.Get(path);

            yield return req.SendWebRequest();

            var data = req.downloadHandler.data;
            if (data == null) yield break;
            // 创建ZipArchive获得具体分类
            using var zip = new ZipArchive(new MemoryStream(data), ZipArchiveMode.Read);

            Queue<(int, BlockModelDefVariant)> notloadedVariant = new();
            List<string> notloadedVariantName = new();
            List<byte> fileData = new();
            byte[] buffer = new byte[1024];
            foreach (var entry in zip.Entries)
            {
                var entryPath = entry.FullName;
                var (ns, ty, name, ext) = GetFileInfomations(entryPath);

                if (string.IsNullOrEmpty(ns)
                    || string.IsNullOrEmpty(ty)
                    || string.IsNullOrEmpty(name)
                    || string.IsNullOrEmpty(ext)) continue;
                if (ty == "Texture" && ext == "png")
                {
                    using var stream = entry.Open();
                    fileData.Clear();
                    int len = 0;
                    do
                    {
                        len = stream.Read(buffer, 0, buffer.Length);
                        fileData.AddRange(buffer[..len]);
                    } while (len == buffer.Length);

                    Texture2D blockTexture = new(0, 0);
                    blockTexture.LoadImage(fileData.ToArray());

                    combinedPack.textures.Add($"{ns}:{name}", blockTexture);
                    continue;
                }

                if (ty == "Model" && ext == "bosmodel")
                {
                    using var reader = new DJReader(new StreamReader(entry.Open()));
                    var env = new DJEnv();
                    env.Put("namespace", new DJString(ns));
                    env.parent = modelBuildEnv;

                    IDJValue evaled = null;
                    try
                    {
                        evaled = reader.EvaluateToEnd(env);
                    }
                    catch (Exception e)
                    {
                        Debug.LogError($"Error while reading: {name}");
                        Debug.LogError(e);
                    }

                    switch (evaled)
                    {
                        case DJRef<BlockModelDef> def:
                            combinedPack.modelDefs.Add($"{ns}:{name}", def.val);
                            break;

                        case DJRef<BlockModelDefVariant> variant:
                            var v = variant.val;
                            if (combinedPack.modelDefs.TryGetValue(v.targetName, out var targetDef))
                            {
                                combinedPack.modelDefs.Add($"{ns}:{name}", v.Produce(targetDef));
                            }
                            else
                            {
                                var idx = notloadedVariantName.Count;
                                notloadedVariantName.Add(v.targetName);
                                notloadedVariant.Enqueue((idx, v));
                            }
                            break;
                    }
                }

                if (ty == "Definition" && ext == "bosblock")
                {
                    using var reader = new DJReader(new StreamReader(entry.Open()));
                    var env = new DJEnv();
                    env.Put("namespace", new DJString(ns));
                    env.parent = modelBuildEnv;

                    combinedPack.blocks.Add($"{ns}:{name}", reader.EvaluateToEnd(env).GetManaged<Block>());
                }
            }

            while (notloadedVariant.Count > 0)
            {
                var (idx, v) = notloadedVariant.Dequeue();
                if (combinedPack.modelDefs.TryGetValue(v.targetName, out var targetDef))
                {
                    combinedPack.modelDefs.Add(notloadedVariantName[idx], v.Produce(targetDef));
                }
                else if (notloadedVariantName.Contains(v.targetName))
                {
                    notloadedVariant.Enqueue((idx, v));
                }
            }
        }

        public (string, string, string, string) GetFileInfomations(string fullName)
        {
            int i = 0;
            for (int j = 0; j < RESOURCES_DIR_NAME.Length; j++)
            {
                if (fullName[i++] != RESOURCES_DIR_NAME[j]) return default;
            }

            i += 1; // Skip '/'

            var builder = new StringBuilder();
            while (i < fullName.Length && fullName[i] != '/')
            {
                builder.Append(fullName[i++]);
            }

            i += 1;

            string nsName = builder.ToString();
            builder.Clear();

            while (i < fullName.Length && fullName[i] != '/')
            {
                builder.Append(fullName[i++]);
            }

            i += 1;

            string resType = builder.ToString();
            builder.Clear();

            while (i < fullName.Length && fullName[i] != '.')
            {
                builder.Append(fullName[i++]);
            }

            i += 1;

            string resName = builder.ToString();
            builder.Clear();

            while (i < fullName.Length)
            {
                builder.Append(fullName[i++]);
            }

            string extensionName = builder.ToString();

            return (nsName, resType, resName, extensionName);
        }
    }
}
